/*
 * Copyright (c) 2016-2019 VMware, Inc. All Rights Reserved.
 *
 * This product is licensed to you under the Apache License, Version 2.0 (the "License").
 * You may not use this product except in compliance with the License.
 *
 * This product may include a number of subcomponents with separate copyright notices
 * and license terms. Your use of these subcomponents is subject to the terms and
 * conditions of the subcomponent's license, as noted in the LICENSE file.
 */

package com.vmware.mangle.java.agent.faults.helpers;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.exception.ExecuteException;
import org.jboss.byteman.rule.helper.Helper;

import com.vmware.mangle.java.agent.faults.AgentFault;
import com.vmware.mangle.java.agent.utils.ThreadUtils;

/**
 * @author hkilari
 *
 *         Helper Class with Utility Methods required by Aspects automatically generated by
 *         fiaasco-aop-agent
 */
public class BytemanRuleHelper extends Helper {
    protected BytemanRuleHelper(Rule rule) {
        super(rule);
    }

    private static final Logger LOG = Logger.getLogger(BytemanRuleHelper.class.getName());

    public void threadSleep(long delayInterval) {
        ThreadUtils.delayInMilliSeconds(delayInterval);
    }

    public void sleep(long delayInterval) {
        long start = new Date().getTime();
        while (new Date().getTime() - start < delayInterval) {
        }
    }

    public void interrupt() {
        Thread.currentThread().interrupt();
    }

    public void killThread() {
        throw new ExecuteException("killing thread " + Thread.currentThread().getName());
    }

    public void killJVM() {
        java.lang.Runtime.getRuntime().halt(-1);
    }

    public void threadDump(String filePath) {
        LOG.info("Capturing ThreadDump at " + filePath);
        ThreadDumpUtils.dump(filePath);
        LOG.info("Sucessfully Captured ThreadDump.");
    }

    public void logMessage(String message) {
        LOG.info(message);
    }

    public void heapDump(String filePath, boolean live) {
        LOG.info("Capturing HeapDump at" + filePath);
        HeapDumpUtils.dumpHeap(filePath, live);
        LOG.info("Sucessfully Captured HeapDump.");
    }

    public void captureTroubleshootingInfoForFaults(String filePath) {
        String threadfilePath = filePath + "-ThreadDump-" + Calendar.getInstance().getTimeInMillis() + ".txt";
        String heapfilePath = filePath + "-HeapDump-" + Calendar.getInstance().getTimeInMillis() + ".bin";
        String faultsInfoPath = filePath + "-FaultsInfo-" + Calendar.getInstance().getTimeInMillis() + ".json";
        for (Entry<String, AgentFault> entry : FaultsHelper.getInstance().getRunningFaults().entrySet()) {
            entry.getValue().appendTaskActivity("Thread Dump captured : " + threadfilePath);
            entry.getValue().appendTaskActivity("Heap Dump captured : " + heapfilePath);
        }
        LOG.info("FaultsInfo captured: " + faultsInfoPath);
        Path path;
        try {
            path = Paths.get(faultsInfoPath);
            Files.write(path, FaultsHelper.objectToJson(FaultsHelper.getInstance().getAllFaultsInfo()).getBytes());
        } catch (IOException e) {
            LOG.severe(e.getMessage());
        }
        ThreadDumpUtils.dump(threadfilePath);
        HeapDumpUtils.dumpHeap(heapfilePath, true);
    }

    @SuppressWarnings("unchecked")
    public void throwException(String exceptionClassName, String exceptionMessage) throws Throwable {
        Class<Throwable> exceptionClass = null;
        try {
            if (exceptionClassName != null && exceptionMessage != null) {
                exceptionClass = (Class<Throwable>) Class.forName(exceptionClassName);
                createException(exceptionClass, exceptionMessage);
            }
            LOG.warning("Unable to Create Exception as Input Validation Failed");
        } catch (ClassNotFoundException e) {
            LOG.severe(e.getMessage());
        }
    }

    public <T extends Throwable> void createException(Class<T> exceptionType, String message) throws T {
        try {
            throw exceptionType.getConstructor(String.class).newInstance(message);
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            LOG.severe(e.getMessage());
        }
    }

    public void serviceLatency(String servicesString, String operation, long delayInterval) {
        List<String> serviceList = tokenizeString(servicesString);
        if (serviceList != null && isFaultEnabledForService(serviceList, operation)) {
            if (delayInterval > 0) {
                sleep(delayInterval);
            }
        }
    }

    public void springServiceLatency(String servicesString, String pathInfo, String httpMethodsString,
            String methodName, long delayInterval) {
        List<String> serviceList = tokenizeString(servicesString);
        List<String> methodList = tokenizeString(httpMethodsString);
        if (serviceList != null && isFaultEnabledForService(serviceList, pathInfo)) {
            if (httpMethodsString != null && isFaultEnabledForHTTPMethod(methodList, methodName)) {
                if (delayInterval > 0) {
                    sleep(delayInterval);
                }
            }
        }
    }

    public boolean isFaultEnabled(String servicesString, String pathInfo, String httpMethodsString, String methodName) {
        List<String> serviceList = tokenizeString(servicesString);
        List<String> methodList = tokenizeString(httpMethodsString);
        if (serviceList != null && isFaultEnabledForService(serviceList, pathInfo)) {
            if (httpMethodsString == null || httpMethodsString.trim().equals("")) {
                return true;
            }
            if (httpMethodsString != null && isFaultEnabledForHTTPMethod(methodList, methodName)) {
                return true;
            }
        }
        return false;
    }

    private List<String> tokenizeString(String inputString) {
        List<String> stringList = new ArrayList<>();
        StringTokenizer stringTokenizer = new StringTokenizer(inputString, "#");
        while (stringTokenizer.hasMoreTokens()) {
            stringList.add(stringTokenizer.nextToken());
        }
        return stringList;
    }

    public static boolean isFaultEnabledForService(List<String> servicesList, String operation) {
        for (String service : servicesList) {
            if (operation.contains(service)) {
                return true;
            }
        }
        return false;
    }

    public static boolean isFaultEnabledForHTTPMethod(List<String> methodList, String methodName) {
        for (String service : methodList) {
            if (methodName.equalsIgnoreCase(service)) {
                return true;
            }
        }
        return false;
    }

    public boolean applyPercentageFilter(String servicesString, String operation, int percent) {
        Random rand = new Random();
        return applyServiceFilter(servicesString, operation) && (rand.nextInt(100) <= percent);
    }

    public boolean applyServiceFilter(String servicesString, String operation) {
        List<String> serviceList = new ArrayList<>();
        StringTokenizer stringTokenizer = new StringTokenizer(servicesString, "#");
        while (stringTokenizer.hasMoreTokens()) {
            serviceList.add(stringTokenizer.nextToken());
        }
        return serviceList != null && isFaultEnabledForService(serviceList, operation);
    }

    public String objectToJson(Object object) {
        return FaultsHelper.objectToJson(object);
    }

    public Object jsonToObject(String className, String jsonString) {
        return FaultsHelper.jsonToObject(className, jsonString);
    }
}
